<?php
/**
 * 视频转换工作脚本
 * 从Redis队列获取任务并执行FFmpeg转换
 * 兼容Windows和Linux系统
 */

// 定义应用目录
define('APP_PATH', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR);
// 定义根目录
define('ROOT_PATH', __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR);
// 定义扩展目录
define('EXTEND_PATH', ROOT_PATH . 'extend' . DIRECTORY_SEPARATOR);
// 不使用ThinkPHP框架的完整启动流程，而是直接加载需要的类
// 这样可以避免被ThinkPHP的控制台命令接管
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'thinkphp' . DIRECTORY_SEPARATOR . 'base.php';

// 手动加载数据库配置
$databaseConfig = include APP_PATH . 'database.php';
// 设置数据库配置
\think\Config::set('database', $databaseConfig);

use think\Db;
use delayQueue\DelayQueue;
use Exception;

// 主函数
function main() {
    echo "视频转换工作进程已启动，开始监听队列...\n";
    
    // 记录启动日志
    logMessage("视频转换工作进程已启动");

    // 创建延迟队列实例
    $queue = null;
    $maxRetry = 3;
    $retryCount = 0;
    
    // 尝试多次连接Redis
    while ($retryCount < $maxRetry) {
        try {
            $queue = new DelayQueue('video_convert_queue');
            echo "成功连接到Redis队列\n";
            logMessage("成功连接到Redis队列");
            break;
        } catch (Exception $e) {
            $errorMsg = "连接Redis队列失败 (尝试 " . ($retryCount + 1) . "/" . $maxRetry . "): " . $e->getMessage();
            echo $errorMsg . "\n";
            logMessage($errorMsg, true);
            
            // 等待5秒后重试
            sleep(5);
            $retryCount++;
        }
    }
    
    // 如果仍然无法连接，记录致命错误并退出
    if ($queue === null) {
        $errorMsg = "无法连接到Redis队列，已达到最大重试次数，程序将退出";
        echo $errorMsg . "\n";
        logMessage($errorMsg, true);
        return;
    }

    // 持续运行
    while (true) {
        try {
            // 每10次循环记录一次心跳日志
            static $loopCount = 0;
            $loopCount++;
            if ($loopCount % 10 == 0) {
                logMessage("工作进程正常运行中，已循环 $loopCount 次");
            }
            
            // 从队列获取任务
            $tasks = $queue->getTask();

            if (!empty($tasks)) {
                foreach ($tasks as $taskJson) {
                    // 解析任务数据
                    $task = json_decode($taskJson, true);

                    if (!empty($task) && isset($task['task_params'])) {
                        $taskParams = $task['task_params'];

                        // 执行转换任务
                        processVideoConvertTask($queue, $taskJson, $taskParams);
                    }
                }
            }

            // 休眠1秒后继续检查
            sleep(1);

        } catch (Exception $e) {
            // 记录错误
            $errorMsg = "处理队列任务时出错: " . $e->getMessage();
            logMessage($errorMsg, true);
            echo $errorMsg . "\n";

            // 发生错误时休眠5秒再继续
            sleep(5);
        }
    }
}

/**
 * 记录日志到文件
 */
function logMessage($message, $isError = false) {
    $timestamp = date('Y-m-d H:i:s');
    $logLevel = $isError ? 'ERROR' : 'INFO';
    $logMessage = "$timestamp [$logLevel] $message" . PHP_EOL;
    
    $logFilePath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR;
    $logFilePath .= $isError ? 'video_convert_error.log' : 'video_convert.log';
    
    // 确保日志目录存在
    if (!is_dir(dirname($logFilePath))) {
        mkdir(dirname($logFilePath), 0777, true);
    }
    
    file_put_contents($logFilePath, $logMessage, FILE_APPEND);
}

// 执行主函数
main();

/**
 * 处理单个视频转换任务
 */
function processVideoConvertTask($queue, $taskJson, $taskParams) {
    $fileId = $taskParams['file_id'];
    $mp4FilePath = $taskParams['mp4_file_path'];
    $m3u8FilePath = $taskParams['m3u8_file_path'];

    echo "处理视频转换任务，文件ID: {$fileId}\n";

    try {
        // 将相对路径转换为绝对路径，解决Windows路径问题
        $absMp4FilePath = $mp4FilePath;
        $absM3u8FilePath = $m3u8FilePath;

        // 如果是相对路径，转换为绝对路径（包含public目录）
        if (strpos($mp4FilePath, './') === 0) {
            $absMp4FilePath = ROOT_PATH . 'public/' . substr($mp4FilePath, 2);
        }
        if (strpos($m3u8FilePath, './') === 0) {
            $absM3u8FilePath = ROOT_PATH . 'public/' . substr($m3u8FilePath, 2);
        }

        // 针对Windows系统，处理路径分隔符
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            $absMp4FilePath = str_replace('/', '\\', $absMp4FilePath);
            $absM3u8FilePath = str_replace('/', '\\', $absM3u8FilePath);
        }

        echo "检查文件路径: {$absMp4FilePath}\n";

        // 检查文件是否存在
        if (!file_exists($absMp4FilePath)) {
            throw new Exception("源MP4文件不存在: {$mp4FilePath} (绝对路径: {$absMp4FilePath})");
        }

        // 检查FFmpeg是否可用
        // 在Windows系统上，需要确保FFmpeg在环境变量中或者使用绝对路径
        $ffmpegCmd = 'ffmpeg -version';
        // 针对Windows系统的特殊处理
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            // 在Windows上使用2>&1将stderr重定向到stdout
            $ffmpegCmd = 'ffmpeg -version 2>&1';
        }
        exec($ffmpegCmd, $ffmpegOutput, $ffmpegReturn);
        if ($ffmpegReturn !== 0) {
            throw new Exception('FFmpeg未安装或不可用，请确保FFmpeg已添加到系统环境变量中');
        }

        // 执行FFmpeg转换
        echo "开始转换视频为M3U8格式...\n";

        // 获取视频信息以确定是否需要调整分辨率
        $videoInfoCmd = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 
            "ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 \"{$absMp4FilePath}\" 2>&1" : 
            "ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 \"{$absMp4FilePath}\"";
        exec($videoInfoCmd, $videoInfoOutput, $videoInfoReturn);
        
        $videoDimensions = !empty($videoInfoOutput) ? explode('x', $videoInfoOutput[0]) : [0, 0];
        $videoWidth = isset($videoDimensions[0]) ? (int)$videoDimensions[0] : 0;
        $videoHeight = isset($videoDimensions[1]) ? (int)$videoDimensions[1] : 0;
        
        // 构建FFmpeg命令，添加内存优化参数
        $additionalParams = '';
        
        // 如果视频分辨率过高，进行缩放以减少内存使用
        if ($videoWidth > 1920 || $videoHeight > 1080) {
            // 保持宽高比，将最长边限制为1080
            $additionalParams .= ' -vf "scale=\'if(gt(a,16/9),1920,-2)\':\'if(gt(a,16/9),-2,1080)\'"';
        }
        
        // 添加比特率控制和编码优化参数
        $additionalParams .= ' -maxrate 5M -bufsize 10M -preset ultrafast -crf 28';
        
        // 根据操作系统构建不同的命令
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            $ffmpegCmd = "ffmpeg -i \"{$absMp4FilePath}\"{$additionalParams} -hls_time 10 -hls_list_size 0 -f hls \"{$absM3u8FilePath}\" 2>&1";
        } else {
            $ffmpegCmd = "ffmpeg -i \"{$absMp4FilePath}\"{$additionalParams} -hls_time 10 -hls_list_size 0 -f hls \"{$absM3u8FilePath}\"";
        }
        echo "执行命令: {$ffmpegCmd}\n";
        exec($ffmpegCmd, $output, $returnVar);

        if ($returnVar !== 0) {
            throw new Exception('视频转换失败，FFmpeg返回非零状态码: ' . $returnVar . '，输出: ' . implode('\n', $output));
        }

        // 检查转换后的M3U8文件是否存在
        if (!file_exists($absM3u8FilePath)) {
            throw new Exception('转换完成但M3U8文件不存在: ' . $m3u8FilePath . ' (绝对路径: ' . $absM3u8FilePath . ')');
        }

        // 更新数据库记录
        $relativePath = substr($m3u8FilePath, 1); // 相对路径
        
        // 处理中文编码问题
        $successMessage = mb_convert_encoding('视频转换成功', 'UTF-8', 'auto');
        $updateResult = Db::name('file')
            ->where('id', $fileId)
            ->update([
                         'path' => $relativePath,
                         'status' => 2, // 2: 处理完成
                         'process_message' => $successMessage,
                         'update_time' => time()
                     ]);
        
        // 同时更新vi_meal_video表中的记录
        // 首先获取file表中的原始路径
        $fileInfo = Db::name('file')->where('id', $fileId)->find();
        if ($fileInfo && !empty($fileInfo['original_path'])) {
            // 使用原始路径查找并更新vi_meal_video表
            $mealVideoResult = Db::name('meal_video')
                ->where('path', $fileInfo['original_path'])
                ->update([
                             'path' => $relativePath, // 更新为M3U8路径
                             'convert_status' => 2, // 2: 处理完成
                             'update_time' => time()
                         ]);
            
            if ($mealVideoResult) {
                echo "成功更新vi_meal_video表，文件ID: {$fileId}
";
            } else {
                echo "未找到对应的vi_meal_video记录，文件ID: {$fileId}
";
            }
        }

        if ($updateResult === false) {
            throw new Exception('更新数据库记录失败');
        }

        // 从队列中删除已完成的任务
        $queue->delTask($taskJson);

        echo "视频转换任务完成，文件ID: {$fileId}\n";

    } catch (Exception $e) {
        // 更新数据库记录为失败状态，处理中文编码问题
        $errorMessage = '转换失败: ' . $e->getMessage();
        // 确保中文字符能正确存储到数据库
        $errorMessage = mb_convert_encoding($errorMessage, 'UTF-8', 'auto');
        Db::name('file')
            ->where('id', $fileId)
            ->update([
                         'status' => 3, // 3: 处理失败
                         'process_message' => $errorMessage,
                         'update_time' => time()
                     ]);
        
        // 同时更新vi_meal_video表中的记录为失败状态
        // 首先获取file表中的原始路径
        $fileInfo = Db::name('file')->where('id', $fileId)->find();
        if ($fileInfo && !empty($fileInfo['original_path'])) {
            // 使用原始路径查找并更新vi_meal_video表
            $mealVideoResult = Db::name('meal_video')
                ->where('path', $fileInfo['original_path'])
                ->update([
                             'convert_status' => 3, // 3: 处理失败
                             'update_time' => time()
                         ]);
            
            if ($mealVideoResult) {
                echo "成功更新vi_meal_video表为失败状态，文件ID: {$fileId}
";
            }
        }

        // 从队列中删除失败的任务
        $queue->delTask($taskJson);

        // 记录错误
        $errorMsg = date('Y-m-d H:i:s') . ' - 处理视频转换任务失败，文件ID: ' . $fileId . '，错误: ' . $e->getMessage() . PHP_EOL;
        $logFilePath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'video_convert_error.log';
        // 确保日志目录存在
        if (!is_dir(dirname($logFilePath))) {
            mkdir(dirname($logFilePath), 0777, true);
        }
        file_put_contents($logFilePath, $errorMsg, FILE_APPEND);
        echo $errorMsg;
    }
}

// 运行主函数
main();